<template>
  <div class="crossfadeBox"></div>
</template>

<script>
import CONTANT from "@/lib/contant.js";
import * as THREE from "@/static/three.module.js";
import { TWEEN } from "@/static/libs/tween.module.min.js";

export default {
  mounted() {
    let container;
    let renderer;
    let transition;

    const transitionParams = {
      useTexture: true,
      transition: 0,
      texture: 5,
      cycle: true,
      animate: true,
      threshold: 0.3,
    };

    const clock = new THREE.Clock();

    init();
    animate();

    function init() {
      container = document.querySelector(".crossfadeBox");

      renderer = new THREE.WebGLRenderer({ antialias: true });
      renderer.setPixelRatio(window.devicePixelRatio);
      renderer.setSize(
        window.innerWidth - CONTANT.LEFT_WIDTH,
        window.innerHeight - CONTANT.TOP_HEIGHT
      );
      container.appendChild(renderer.domElement);

      const geometryA = new THREE.BoxGeometry(2, 2, 2);
      const geometryB = new THREE.IcosahedronGeometry(1, 1);
      const sceneA = new FXScene(
        geometryA,
        new THREE.Vector3(0, -0.4, 0),
        0xffffff
      );
      const sceneB = new FXScene(
        geometryB,
        new THREE.Vector3(0, 0.2, 0.1),
        0x000000
      );

      transition = new Transition(sceneA, sceneB);
    }

    function animate() {
      requestAnimationFrame(animate);
      render();
    }

    function render() {
      transition.render(clock.getDelta());
    }

    function generateInstancedMesh(geometry, material, count) {
      const mesh = new THREE.InstancedMesh(geometry, material, count);

      const dummy = new THREE.Object3D();
      const color = new THREE.Color();

      for (let i = 0; i < count; i++) {
        dummy.position.x = Math.random() * 10000 - 5000;
        dummy.position.y = Math.random() * 6000 - 3000;
        dummy.position.z = Math.random() * 8000 - 4000;

        dummy.rotation.x = Math.random() * 2 * Math.PI;
        dummy.rotation.y = Math.random() * 2 * Math.PI;
        dummy.rotation.z = Math.random() * 2 * Math.PI;

        dummy.scale.x = Math.random() * 200 + 100;

        if (geometry.type === "BoxGeometry") {
          dummy.scale.y = Math.random() * 200 + 100;
          dummy.scale.z = Math.random() * 200 + 100;
        } else {
          dummy.scale.y = dummy.scale.x;
          dummy.scale.z = dummy.scale.x;
        }

        dummy.updateMatrix();

        mesh.setMatrixAt(i, dummy.matrix);
        mesh.setColorAt(i, color.setScalar(0.1 + 0.9 * Math.random()));
      }

      return mesh;
    }

    function FXScene(geometry, rotationSpeed, clearColor) {
      this.clearColor = clearColor;

      const camera = new THREE.PerspectiveCamera(
        50,
        (window.innerWidth - CONTANT.LEFT_WIDTH) /
          (window.innerHeight - CONTANT.TOP_HEIGHT),
        1,
        10000
      );
      camera.position.z = 2000;

      const scene = new THREE.Scene();
      scene.add(new THREE.AmbientLight(0x555555));

      const light = new THREE.SpotLight(0xffffff, 1.5);
      light.position.set(0, 500, 2000);
      scene.add(light);

      this.rotationSpeed = rotationSpeed;

      const color = geometry.type === "BoxGeometry" ? 0x0000ff : 0xff0000;
      const material = new THREE.MeshPhongMaterial({
        color: color,
        flatShading: true,
      });
      const mesh = generateInstancedMesh(geometry, material, 500);
      scene.add(mesh);

      const renderTargetParameters = {
        minFilter: THREE.LinearFilter,
        magFilter: THREE.LinearFilter,
        format: THREE.RGBFormat,
      };
      this.fbo = new THREE.WebGLRenderTarget(
        window.innerWidth - CONTANT.LEFT_WIDTH,
        window.innerHeight - CONTANT.TOP_HEIGHT,
        renderTargetParameters
      );

      this.render = function (delta, rtt) {
        mesh.rotation.x += delta * this.rotationSpeed.x;
        mesh.rotation.y += delta * this.rotationSpeed.y;
        mesh.rotation.z += delta * this.rotationSpeed.z;
        renderer.setClearColor(this.clearColor);
        if (rtt) {
          renderer.setRenderTarget(this.fbo);
          renderer.clear();
          renderer.render(scene, camera);
        } else {
          renderer.setRenderTarget(null);
          renderer.render(scene, camera);
        }
      };
    }

    function Transition(sceneA, sceneB) {
      const scene = new THREE.Scene();
      const width = window.innerWidth - CONTANT.LEFT_WIDTH;
      const height = window.innerHeight - CONTANT.TOP_HEIGHT;
      const camera = new THREE.OrthographicCamera(
        width / -2,
        width / 2,
        height / 2,
        height / -2,
        -10,
        10
      );

      const textures = [];
      const loader = new THREE.TextureLoader();
      for (let i = 0; i < 6; i++) {
        textures[i] = loader.load(
          CONTANT.URLPATH + "/textures/transition/transition" + (i + 1) + ".png"
        );
      }

      const material = new THREE.ShaderMaterial({
        uniforms: {
          tDiffuse1: {
            value: null,
          },
          tDiffuse2: {
            value: null,
          },
          mixRatio: {
            value: 0.0,
          },
          threshold: {
            value: 0.1,
          },
          useTexture: {
            value: 1,
          },
          tMixTexture: {
            value: textures[0],
          },
        },
        vertexShader: [
          "varying vec2 vUv;",
          "void main() {",
          "vUv = vec2( uv.x, uv.y );",
          "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
          "}",
        ].join("\n"),
        fragmentShader: [
          "uniform float mixRatio;",
          "uniform sampler2D tDiffuse1;",
          "uniform sampler2D tDiffuse2;",
          "uniform sampler2D tMixTexture;",
          "uniform int useTexture;",
          "uniform float threshold;",
          "varying vec2 vUv;",
          "void main() {",
          "	vec4 texel1 = texture2D( tDiffuse1, vUv );",
          "	vec4 texel2 = texture2D( tDiffuse2, vUv );",
          "	if (useTexture==1) {",
          "		vec4 transitionTexel = texture2D( tMixTexture, vUv );",
          "		float r = mixRatio * (1.0 + threshold * 2.0) - threshold;",
          "		float mixf=clamp((transitionTexel.r - r)*(1.0/threshold), 0.0, 1.0);",
          "		gl_FragColor = mix( texel1, texel2, mixf );",
          "	} else {",
          "		gl_FragColor = mix( texel2, texel1, mixRatio );",
          "	}",
          "}",
        ].join("\n"),
      });

      const geometry = new THREE.PlaneGeometry(
        window.innerWidth - CONTANT.LEFT_WIDTH,
        window.innerHeight - CONTANT.TOP_HEIGHT
      );
      const mesh = new THREE.Mesh(geometry, material);
      scene.add(mesh);

      material.uniforms.tDiffuse1.value = sceneA.fbo.texture;
      material.uniforms.tDiffuse2.value = sceneB.fbo.texture;

      new TWEEN.Tween(transitionParams)
        .to({ transition: 1 }, 1500)
        .repeat(Infinity)
        .delay(2000)
        .yoyo(true)
        .start();

      this.needsTextureChange = false;

      this.setTextureThreshold = function (value) {
        material.uniforms.threshold.value = value;
      };

      this.useTexture = function (value) {
        material.uniforms.useTexture.value = value ? 1 : 0;
      };

      this.setTexture = function (i) {
        material.uniforms.tMixTexture.value = textures[i];
      };

      this.render = function (delta) {
        if (transitionParams.animate) {
          TWEEN.update();
          if (transitionParams.cycle) {
            if (
              transitionParams.transition == 0 ||
              transitionParams.transition == 1
            ) {
              if (this.needsTextureChange) {
                transitionParams.texture =
                  (transitionParams.texture + 1) % textures.length;
                material.uniforms.tMixTexture.value =
                  textures[transitionParams.texture];
                this.needsTextureChange = false;
              }
            } else {
              this.needsTextureChange = true;
            }
          } else {
            this.needsTextureChange = true;
          }
        }

        material.uniforms.mixRatio.value = transitionParams.transition;
        if (transitionParams.transition == 0) {
          sceneB.render(delta, false);
        } else if (transitionParams.transition == 1) {
          sceneA.render(delta, false);
        } else {
          sceneA.render(delta, true);
          sceneB.render(delta, true);
          renderer.setRenderTarget(null);
          renderer.clear();
          renderer.render(scene, camera);
        }
      };
    }
  },
};
</script>

<style>
.crossfadeBox {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
}
</style>
